package personal.parser;

import exceptions.DividedByZeroException;

import java.util.LinkedList;
import java.util.List;

/**
 * 语义分析
 */
public class SemanticAnalyzer {

    private final String delimiter = "delimiter";

    private final Grammar grammar;

    public SemanticAnalyzer(Grammar grammar) {
        this.grammar = grammar;
    }

    /**
     * 语义分析，负责调整 token list，即按照归约项弹出相关 token，并放入计算后的 token
     *
     * @param codes      中间代码栈
     * @param production 归约内容
     */
    public void analysis(final List<String> codes, final Grammar.Production production) throws DividedByZeroException {
        // 先弹出归约项长度的中间代码，放入 buffer 中
        LinkedList<String> buffer = new LinkedList<>();
        for (int i = 0; i < production.rightSide().size(); ++i) {
            buffer.addFirst(codes.remove(codes.size() - 1));
        }
        // 处理各条产生式
        double ArithExprL, ArithExprR;
        boolean BoolExprL, BoolExprR;
        int pid = this.grammar.getProductions().indexOf(production); // 产生式编号
        switch (pid) {
            case 0: // "Expr  ArithExpr"
            case 1: // "ArithExpr  decimal"
            case 10: // "ArithExpr  UnaryFunc"
            case 11:// "ArithExpr  VariablFunc"
            case 16: // "ArithExprList  ArithExpr"
            case 18: // "BoolExpr  true"
            case 19: // "BoolExpr  false"
                codes.add(buffer.getFirst());
                break;
            case 2: // "ArithExpr  (  ArithExpr  )"
            case 20: // "BoolExpr  (  BoolExpr  )"
                codes.add(buffer.get(1));
                break;
            case 3: // "ArithExpr  ArithExpr  +  ArithExpr"
            case 4: // "ArithExpr  ArithExpr  -  ArithExpr"
            case 5: // "ArithExpr  ArithExpr  *  ArithExpr"
            case 7: // "ArithExpr  ArithExpr  ^  ArithExpr"
                ArithExprL = Double.parseDouble(buffer.get(0));
                ArithExprR = Double.parseDouble(buffer.get(2));
                switch (buffer.get(1)) {
                    case "+" -> codes.add(Double.toString(ArithExprL + ArithExprR));
                    case "-" -> codes.add(Double.toString(ArithExprL - ArithExprR));
                    case "*" -> codes.add(Double.toString(ArithExprL * ArithExprR));
                    case "^" -> codes.add(Double.toString(Math.pow(ArithExprL, ArithExprR)));
                    default -> throw new UnknownError("不是+-*/^");
                }
                break;
            case 6: // "ArithExpr  ArithExpr  /  ArithExpr"
                ArithExprL = Double.parseDouble(buffer.get(0));
                ArithExprR = Double.parseDouble(buffer.get(2));
                if (ArithExprR == 0) throw new DividedByZeroException();
                codes.add(Double.toString(ArithExprL / ArithExprR));
                break;
            case 8: // "ArithExpr  neg  ArithExpr"
                ArithExprR = Double.parseDouble(buffer.get(1));
                codes.add(Double.toString(-ArithExprR));
                break;
            case 9: // "ArithExpr  BoolExpr  ?  ArithExpr  :  ArithExpr"
                BoolExprL = Boolean.parseBoolean(buffer.get(0));
                ArithExprL = Double.parseDouble(buffer.get(2));
                ArithExprR = Double.parseDouble(buffer.get(4));
                if (BoolExprL) codes.add(Double.toString(ArithExprL));
                else codes.add(Double.toString(ArithExprR));
                break;

            case 12: // "UnaryFunc  sin  (  ArithExpr  )"
                ArithExprL = Double.parseDouble(buffer.get(2));
                codes.add(Double.toString(Math.sin(ArithExprL)));
                break;
            case 13: // "UnaryFunc  cos  (  ArithExpr  )"
                ArithExprL = Double.parseDouble(buffer.get(2));
                codes.add(Double.toString(Math.cos(ArithExprL)));
                break;
            case 14: // "VariablFunc  max  (  ArithExpr  ,  ArithExprList  )"
            case 15: // "VariablFunc  min  (  ArithExpr  ,  ArithExprList  )"
                ArithExprL = Double.parseDouble(buffer.get(2));
//                System.out.println(buffer.get(4)); // debug
//                System.out.println(buffer.get(4).split(this.delimiter).length); // debug
                for (String expr : buffer.get(4).split(this.delimiter)) {
//                    System.out.println("debug:" + expr + ":debug"); // debug
                    switch (buffer.get(0)) {
                        case "min" -> ArithExprL = Math.min(ArithExprL, Double.parseDouble(expr));
                        case "max" -> ArithExprL = Math.max(ArithExprL, Double.parseDouble(expr));
                        default -> throw new UnknownError("不是min、max");
                    }
                }
                codes.add(Double.toString(ArithExprL));
                break;
            case 17: // "ArithExprList  ArithExpr  ,  ArithExprList"
                codes.add(buffer.get(0) + this.delimiter + buffer.get(2)); // todo
                break;
            case 21: // "BoolExpr  ArithExpr  >  ArithExpr"
            case 22: // "BoolExpr  ArithExpr  >=  ArithExpr"
            case 23: // "BoolExpr  ArithExpr  <  ArithExpr"
            case 24: // "BoolExpr  ArithExpr  <=  ArithExpr"
            case 25: // "BoolExpr  ArithExpr  =  ArithExpr"
            case 26: // "BoolExpr  ArithExpr  <>  ArithExpr"
                ArithExprL = Double.parseDouble(buffer.get(0));
                ArithExprR = Double.parseDouble(buffer.get(2));
                switch (buffer.get(1)) {
                    case ">" -> codes.add(Boolean.toString(ArithExprL > ArithExprR));
                    case ">=" -> codes.add(Boolean.toString(ArithExprL >= ArithExprR));
                    case "<" -> codes.add(Boolean.toString(ArithExprL < ArithExprR));
                    case "<=" -> codes.add(Boolean.toString(ArithExprL <= ArithExprR));
                    case "=" -> codes.add(Boolean.toString(ArithExprL == ArithExprR));
                    case "<>" -> codes.add(Boolean.toString(ArithExprL != ArithExprR));
                    default -> throw new UnknownError("<=>");
                }
                break;
            case 27: // "BoolExpr  BoolExpr  &  BoolExpr"
                BoolExprL = Boolean.parseBoolean(buffer.get(0));
                BoolExprR = Boolean.parseBoolean(buffer.get(2));
                codes.add(Boolean.toString(BoolExprL & BoolExprR));
                break;
            case 28: // "BoolExpr  BoolExpr  |  BoolExpr"
                BoolExprL = Boolean.parseBoolean(buffer.get(0));
                BoolExprR = Boolean.parseBoolean(buffer.get(2));
                codes.add(Boolean.toString(BoolExprL | BoolExprR));
                break;
            case 29: // "BoolExpr  !  BoolExpr"
                BoolExprL = Boolean.parseBoolean(buffer.get(1));
                codes.add(Boolean.toString(!BoolExprL));
                break;
        }
    }
}
